using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq.Expressions;
using System.Reflection;

namespace BNode.Expressions
{

    public static class ExprBuilderExtensions
    {
        public static Expression<Func<TResult>> Func<TResult>(this ExprBuilder builder) => builder.Lambda<Func<TResult>>();
        public static Expression<Func<T1, TResult>> Func<T1, TResult>(this ExprBuilder builder) => builder.Lambda<Func<T1, TResult>>();
        public static Expression<Func<T1, T2, TResult>> Func<T1, T2, TResult>(this ExprBuilder builder) => builder.Lambda<Func<T1, T2, TResult>>();
        public static Expression<Func<T1, T2, T3, TResult>> Func<T1, T2, T3, TResult>(this ExprBuilder builder) => builder.Lambda<Func<T1, T2, T3, TResult>>();
        public static Expression<Func<T1, T2, T3, T4, TResult>> Func<T1, T2, T3, T4, TResult>(this ExprBuilder builder) => builder.Lambda<Func<T1, T2, T3, T4, TResult>>();
        public static Expression<Func<T1, T2, T3, T4, T5, TResult>> Func<T1, T2, T3, T4, T5, TResult>(this ExprBuilder builder) => builder.Lambda<Func<T1, T2, T3, T4, T5, TResult>>();
        public static Expression<Func<T1, T2, T3, T4, T5, T6, TResult>> Func<T1, T2, T3, T4, T5, T6, TResult>(this ExprBuilder builder) => builder.Lambda<Func<T1, T2, T3, T4, T5, T6, TResult>>();
        public static Expression<Action> Action(this ExprBuilder builder) => builder.Lambda<Action>();
        public static Expression<Action<T1>> Action<T1>(this ExprBuilder builder) => builder.Lambda<Action<T1>>();
        public static Expression<Action<T1, T2>> Action<T1, T2>(this ExprBuilder builder) => builder.Lambda<Action<T1, T2>>();
        public static Expression<Action<T1, T2, T3>> Action<T1, T2, T3>(this ExprBuilder builder) => builder.Lambda<Action<T1, T2, T3>>();
        public static Expression<Action<T1, T2, T3, T4>> Action<T1, T2, T3, T4>(this ExprBuilder builder) => builder.Lambda<Action<T1, T2, T3, T4>>();
        public static Expression<Action<T1, T2, T3, T4, T5>> Action<T1, T2, T3, T4, T5>(this ExprBuilder builder) => builder.Lambda<Action<T1, T2, T3, T4, T5>>();
        public static Expression<Action<T1, T2, T3, T4, T5, T6>> Action<T1, T2, T3, T4, T5, T6>(this ExprBuilder builder) => builder.Lambda<Action<T1, T2, T3, T4, T5, T6>>();
        public static Expression<Predicate<T1>> Predicate<T1>(this ExprBuilder builder) => builder.Lambda<Predicate<T1>>();
        public static ExprBuilder AddAssign(this ExprBuilder builder, ParameterExpression target, object value) => builder.AddAssign(target, value.Constant());
        public static ExprBuilder Assign(this ExprBuilder builder, ParameterExpression target, object value) => builder.Assign(target, value.Constant());
        public static ExprBuilder DeclareVariable(this ExprBuilder builder, Type type, out ParameterExpression variable, string name, object value)
            => builder.DeclareVariable(type, out variable, name, value.Constant());
        public static ExprBuilder DeclareVariable(this ExprBuilder builder, Type type, string name, object value)
            => builder.DeclareVariable(type, name, value.Constant());
        public static ExprBuilder DeclareVariable<T>(this ExprBuilder builder, string name, Expression value)
            => builder.DeclareVariable(typeof(T), name, value);
        public static ExprBuilder DeclareVariable<T>(this ExprBuilder builder, string name, object value)
            => builder.DeclareVariable(typeof(T), name, value.Constant());
        public static ExprBuilder DeclareVariable<T>(this ExprBuilder builder, out ParameterExpression variable, string name)
            => builder.DeclareVariable(typeof(T), out variable, name);
        public static ExprBuilder DeclareVariable<T>(this ExprBuilder builder, out ParameterExpression variable, string name, Expression value)
            => builder.DeclareVariable(typeof(T), out variable, name, value);
        public static ExprBuilder DeclareVariable<T>(this ExprBuilder builder, out ParameterExpression variable, string name, object value)
            => builder.DeclareVariable(typeof(T), out variable, name,value.Constant());
        public static ExprBuilder DeclareVariable<T>(this ExprBuilder builder, out ParameterExpression variable, string name, Func<Type, Expression> value)
            => builder.DeclareVariable(typeof(T), out variable, name, value.Invoke(typeof(T)));

        public static ExprBuilder DeclareVariables(this ExprBuilder builder, Type[] paramTypes,Func<int,Expression> indexToValue=null)
        {
            for (int i = 0; i < paramTypes.Length; i++)
            {
                builder.DeclareVariable(paramTypes[i],null,indexToValue?.Invoke(i));
            }
            return builder;
        }
        public static ExprBuilder DeclareVariables(this ExprBuilder builder, Type[] paramTypes, out ParameterExpression[] parameters, Func<int, Expression> indexToValue = null)
        {
            parameters = new ParameterExpression[paramTypes.Length];
            for (int i = 0; i < paramTypes.Length; i++)
            {
                builder.DeclareVariable(paramTypes[i],out parameters[i], null, indexToValue?.Invoke(i));
            }
            return builder;
        }
        public static ExprBuilder GetOrAddVariable<T>(this ExprBuilder builder, out ParameterExpression parameter, string name)
            => builder.GetOrAddVariable(typeof(T), out parameter, name);
        public static ExprBuilder GetOrAddVariable<T>(this ExprBuilder builder, out ParameterExpression parameter, string name, Expression value)
            => builder.GetOrAddVariable(typeof(T), out parameter, name, value);
        public static ExprBuilder GetOrAddVariable<T>(this ExprBuilder builder, out ParameterExpression parameter, string name, object value)
    => builder.GetOrAddVariable(typeof(T), out parameter, name, value.Constant());
        public static ExprBuilder AddParameter<T>(this ExprBuilder builder, string name = null) => builder.AddParameter(typeof(T), name);
        public static ExprBuilder AddParameter<T>(this ExprBuilder builder, out ParameterExpression parameter, string name) => builder.AddParameter(typeof(T), out parameter, name);
        public static ExprBuilder AddParameters(this ExprBuilder builder, Type[] paramTypes)
        {
            for (int i = 0; i < paramTypes.Length; i++)
            {
                builder.AddParameter(paramTypes[i]);
            }
            return builder;
        }
        public static ExprBuilder EnsureConvert<TTarget>(this ExprBuilder builder, MethodInfo method = null) => builder.EnsureConvert(typeof(TTarget), method);
        public static ExprBuilder EnsureConvert<T, TTarget>(this ExprBuilder builder, Func<T, TTarget> convertFunc)
            => builder.EnsureConvert(typeof(TTarget), convertFunc.Method);

        public static ExprBuilder Define<D>(this ExprBuilder builder,out Expression<D> result, Expression<D> expression) where D : Delegate
        {
            if (expression is not LambdaExpression)
            {
                new NotSupportedException("喵！只支持定义lambda调用表达式").Throw();
                result = null;
                return builder;
            }
            result = expression;
            return builder;
        }
        public static InvocationExpression BuildInvokeFunc<TResult>(this ExprBuilder builder, Expression<Func<TResult>> expression, params Expression[] args)
        {
            return builder.BuildInvoke(expression, args);
        }
        public static InvocationExpression BuildInvokeFunc<T1, TResult>(this ExprBuilder builder, Expression<Func<T1, TResult>> expression, params Expression[] args)
        {
            return builder.BuildInvoke(expression, args);
        }
        public static InvocationExpression BuildInvokeFunc<T1, T2, TResult>(this ExprBuilder builder, Expression<Func<T1, T2, TResult>> expression, params Expression[] args)
        {
            return builder.BuildInvoke(expression, args);
        }
        public static InvocationExpression BuildInvokeFunc<T1, T2, T3, TResult>(this ExprBuilder builder, Expression<Func<T1, T2, T3, TResult>> expression, params Expression[] args)
        {
            return builder.BuildInvoke(expression, args);
        }
        public static InvocationExpression BuildInvokeFunc<T1, T2, T3, T4, TResult>(this ExprBuilder builder, Expression<Func<T1, T2, T3, T4, TResult>> expression, params Expression[] args)
        {
            return builder.BuildInvoke(expression, args);
        }
        public static InvocationExpression BuildInvokeFunc<T1, T2, T3, T4, T5, TResult>(this ExprBuilder builder, Expression<Func<T1, T2, T3, T4, T5, TResult>> expression, params Expression[] args)
        {
            return builder.BuildInvoke(expression, args);
        }
        public static InvocationExpression BuildInvokeFunc<T1, T2, T3, T4, T5, T6, TResult>(this ExprBuilder builder, Expression<Func<T1, T2, T3, T4, T5, T6, TResult>> expression, params Expression[] args)
        {
            return builder.BuildInvoke(expression, args);
        }
        public static ExprBuilder InvokeAction(this ExprBuilder builder, Expression<Action> expression, params Expression[] args)
        {
            return builder.Invoke(expression, args);
        }
        public static ExprBuilder InvokeAction<T1>(this ExprBuilder builder, Expression<Action<T1>> expression, params Expression[] args)
        {
            return builder.Invoke(expression, args);
        }
        public static ExprBuilder InvokeAction<T1, T2>(this ExprBuilder builder, Expression<Action<T1, T2>> expression, params Expression[] args)
        {
            return builder.Invoke(expression, args);
        }
        public static ExprBuilder InvokeAction<T1, T2, T3>(this ExprBuilder builder, Expression<Action<T1, T2, T3>> expression, params Expression[] args)
        {
            return builder.Invoke(expression, args);
        }
        public static ExprBuilder InvokeAction<T1, T2, T3, T4>(this ExprBuilder builder, Expression<Action<T1, T2, T3, T4>> expression, params Expression[] args)
        {
            return builder.Invoke(expression, args);
        }
        public static ExprBuilder InvokeAction<T1, T2, T3, T4, T5>(this ExprBuilder builder, Expression<Action<T1, T2, T3, T4, T5>> expression, params Expression[] args)
        {
            return builder.Invoke(expression, args);
        }
        public static ExprBuilder InvokeAction<T1, T2, T3, T4, T5, T6>(this ExprBuilder builder, Expression<Action<T1, T2, T3, T4, T5, T6>> expression, params Expression[] args)
        {
            return builder.Invoke(expression, args);
        }
        public static ExprBuilder InvokeFunc<TResult>(this ExprBuilder builder, Expression<Func<TResult>> expression, params Expression[] args)
        {
            return builder.Invoke(expression, args);
        }
        public static ExprBuilder InvokeFunc<T1, TResult>(this ExprBuilder builder, Expression<Func<T1, TResult>> expression, params Expression[] args)
        {
            return builder.Invoke(expression, args);
        }
        public static ExprBuilder InvokeFunc<T1, T2, TResult>(this ExprBuilder builder, Expression<Func<T1, T2, TResult>> expression, params Expression[] args)
        {
            return builder.Invoke(expression, args);
        }
        public static ExprBuilder InvokeFunc<T1, T2, T3, TResult>(this ExprBuilder builder, Expression<Func<T1, T2, T3, TResult>> expression, params Expression[] args)
        {
            return builder.Invoke(expression, args);
        }
        public static ExprBuilder InvokeFunc<T1, T2, T3, T4, TResult>(this ExprBuilder builder, Expression<Func<T1, T2, T3, T4, TResult>> expression, params Expression[] args)
        {
            return builder.Invoke(expression, args);
        }
        public static ExprBuilder InvokeFunc<T1, T2, T3, T4, T5, TResult>(this ExprBuilder builder, Expression<Func<T1, T2, T3, T4, T5, TResult>> expression, params Expression[] args)
        {
            return builder.Invoke(expression, args);
        }
        public static ExprBuilder InvokeFunc<T1, T2, T3, T4, T5, T6, TResult>(this ExprBuilder builder, Expression<Func<T1, T2, T3, T4, T5, T6, TResult>> expression, params Expression[] args)
        {
            return builder.Invoke(expression, args);
        }
        public static ExprBuilder DefineAction(this ExprBuilder builder, out Expression<Action> result, Expression<Action> expression)
        {
            return builder.Define(out result, expression);
        }
        public static ExprBuilder DefineAction<T1>(this ExprBuilder builder, out Expression<Action<T1>> result, Expression<Action<T1>> expression)
        {
            return builder.Define(out result, expression);
        }
        public static ExprBuilder DefineAction<T1, T2>(this ExprBuilder builder, out Expression<Action<T1, T2>> result, Expression<Action<T1, T2>> expression)
        {
            return builder.Define(out result, expression);
        }
        public static ExprBuilder DefineAction<T1, T2, T3>(this ExprBuilder builder, out Expression<Action<T1, T2, T3>> result, Expression<Action<T1, T2, T3>> expression)
        {
            return builder.Define(out result, expression);
        }
        public static ExprBuilder DefineAction<T1, T2, T3, T4>(this ExprBuilder builder, out Expression<Action<T1, T2, T3, T4>> result, Expression<Action<T1, T2, T3, T4>> expression)
        {
            return builder.Define(out result, expression);
        }
        public static ExprBuilder DefineAction<T1, T2, T3, T4, T5>(this ExprBuilder builder, out Expression<Action<T1, T2, T3, T4, T5>> result, Expression<Action<T1, T2, T3, T4, T5>> expression)
        {
            return builder.Define(out result, expression);
        }
        public static ExprBuilder DefineAction<T1, T2, T3, T4, T5, T6>(this ExprBuilder builder, out Expression<Action<T1, T2, T3, T4, T5, T6>> result, Expression<Action<T1, T2, T3, T4, T5, T6>> expression)
        {
            return builder.Define(out result, expression);
        }
        public static ExprBuilder DefineFunc<TResult>(this ExprBuilder builder, out Expression<Func<TResult>> result, Expression<Func<TResult>> expression)
        {
            return builder.Define(out result, expression);
        }
        public static ExprBuilder DefineFunc<T1, TResult>(this ExprBuilder builder, out Expression<Func<T1, TResult>> result, Expression<Func<T1, TResult>> expression)
        {
            return builder.Define(out result, expression);
        }
        public static ExprBuilder DefineFunc<T1, T2, TResult>(this ExprBuilder builder, out Expression<Func<T1, T2, TResult>> result, Expression<Func<T1, T2, TResult>> expression)
        {
            return builder.Define(out result, expression);
        }
        public static ExprBuilder DefineFunc<T1, T2, T3, TResult>(this ExprBuilder builder, out Expression<Func<T1, T2, T3, TResult>> result, Expression<Func<T1, T2, T3, TResult>> expression)
        {
            return builder.Define(out result, expression);
        }
        public static ExprBuilder DefineFunc<T1, T2, T3, T4, TResult>(this ExprBuilder builder, out Expression<Func<T1, T2, T3, T4, TResult>> result, Expression<Func<T1, T2, T3, T4, TResult>> expression)
        {
            return builder.Define(out result, expression);
        }
        public static ExprBuilder DefineFunc<T1, T2, T3, T4, T5, TResult>(this ExprBuilder builder, out Expression<Func<T1, T2, T3, T4, T5, TResult>> result, Expression<Func<T1, T2, T3, T4, T5, TResult>> expression)
        {
            return builder.Define(out result, expression);
        }
        public static ExprBuilder DefineFunc<T1, T2, T3, T4, T5, T6, TResult>(this ExprBuilder builder, out Expression<Func<T1, T2, T3, T4, T5, T6, TResult>> result, Expression<Func<T1, T2, T3, T4, T5, T6, TResult>> expression)
        {
            return builder.Define(out result, expression);
        }
        public static Expression ToSingleExpression(this ExprBuilder builder, out Expression returnValue) 
            => builder.ToSingleExpression(typeof(void), out returnValue);
        public static Expression ToSingleExpression(this ExprBuilder builder) 
            => builder.ToSingleExpression(typeof(void));
        public static ExprBuilder Swap(this ExprBuilder builder, Expression a, Expression b)
            => builder.GetOrAddVariable(typeof(int),out var temp, "temp", a).Assign(a, b).Assign(b, temp);

        /// <summary>
        /// 对于每个循环项，操作表达式树。关键变量：itemVar循环项，place第几个项
        /// </summary>
        public static ExprBuilder ForEachRaw(this ExprBuilder exprBuilder, Expression enumerable, Action<ExprBuilder> loopBodyAct, Predicate<object> getBreakCondition = null, Predicate<object> getContinueCondition = null)
        {
            var elementType = enumerable.Type.GetGenericArgumentOrElementType();
            var child = new ExprBuilder(exprBuilder);
            var getEnumerator = child.BuildInvokeFunc<IEnumerable, IEnumerator>(ie => ie.GetEnumerator(), enumerable);
            child.DeclareVariable<IEnumerator>(out var enumeratorVar, "enumeratorVar", getEnumerator);
            child.DeclareVariable(elementType, out var itemVar, "itemVar");
            child.DeclareVariable<int>(out var place, "place", -1);
            var moveNext = child.BuildInvokeFunc<IEnumerator, bool>(itor => itor.MoveNext(), enumeratorVar);
            Action<ExprBuilder> buildAct = builder =>
            {
                builder.Assign(itemVar, enumeratorVar.Property("Current"));
                builder.AddAssign(place, 1);
                if (getBreakCondition != null)
                {
                    var breakCondition = builder.BuildInvokeFunc<object, bool>(t => getBreakCondition(t), itemVar);
                    builder.If(breakCondition, then => then.Break());
                }
                if (getContinueCondition != null)
                {
                    var continueCondition = builder.BuildInvokeFunc<object, bool>(t => getContinueCondition(t), itemVar);
                    builder.If(continueCondition, then => then.Continue());
                }
                loopBodyAct?.Invoke(builder);
            };
            child.While(moveNext, buildAct);
            var loop = child.ToSingleExpression();
            exprBuilder.EInfo.AddExpr(loop);
            return exprBuilder;
        }
        /// <summary>
        /// 对于每个循环项，操作表达式树。关键变量：itemVar循环项，place第几个项
        /// </summary>
        public static ExprBuilder ForEachRaw<T>(this ExprBuilder exprBuilder, Expression enumerable, Action<ExprBuilder> loopBodyAct, Predicate<T> getBreakCondition = null, Predicate<T> getContinueCondition = null)
        {
            var getEnumerator = exprBuilder.BuildInvokeFunc<IEnumerable<T>, IEnumerator<T>>(ie => ie.GetEnumerator(), enumerable);
            var child = new ExprBuilder(exprBuilder);
            child.DeclareVariable<IEnumerator<T>>(out var enumeratorVar, "enumeratorVar", getEnumerator);
            child.DeclareVariable<T>(out var itemVar, "itemVar");
            child.DeclareVariable<int>(out var place, "place", -1);
            var moveNext = child.BuildInvokeFunc<IEnumerator<T>, bool>(itor => itor.MoveNext(), enumeratorVar);
            Action<ExprBuilder> buildAct = builder =>
            {
                builder.Assign(itemVar, enumeratorVar.Property("Current"));
                builder.AddAssign(place, 1);
                if (getBreakCondition != null)
                {
                    var breakCondition = builder.BuildInvokeFunc<T, bool>(t => getBreakCondition(t), itemVar);
                    builder.If(breakCondition, then => then.Break());
                }
                if (getContinueCondition != null)
                {
                    var continueCondition = builder.BuildInvokeFunc<T, bool>(t => getContinueCondition(t), itemVar);
                    builder.If(continueCondition, then => then.Continue());
                }
                loopBodyAct?.Invoke(builder);
            };
            child.While(moveNext, buildAct);
            var loop = child.ToSingleExpression();
            exprBuilder.EInfo.AddExpr(loop);
            return exprBuilder;
        }
        public static ExprBuilder ForEach(this ExprBuilder exprBuilder, Expression enumerable, Action<object> foreachAct, Predicate<object> getBreakCondition=null, Predicate<object> getContinueCondition=null)
        {
            return ForEachRaw(exprBuilder, enumerable
                , b => b.InvokeAction<object>(obj => foreachAct(obj), b.PInfo.Variables("itemVar"))
                , getBreakCondition, getContinueCondition);
        }
        public static ExprBuilder ForEach(this ExprBuilder exprBuilder, Expression enumerable, Action<object,int> foreachAct, Predicate<object> getBreakCondition=null, Predicate<object> getContinueCondition=null)
        {
            return ForEachRaw(exprBuilder, enumerable
                , b => b.InvokeAction<object, int>((obj, count) => foreachAct(obj, count), b.PInfo.Variables("itemVar"), b.PInfo.Variables("place"))
                , getBreakCondition, getContinueCondition);
        }

        public static ExprBuilder ForEach<T>(this ExprBuilder exprBuilder, Expression enumerable, Action<T> foreachAct, Predicate<T> getBreakCondition = null, Predicate<T> getContinueCondition = null)
        {
            return ForEachRaw(exprBuilder, enumerable
                , b => b.InvokeAction<T>(obj => foreachAct(obj), b.PInfo.Variables("itemVar"))
                , getBreakCondition, getContinueCondition);
        }
        public static ExprBuilder ForEach<T>(this ExprBuilder exprBuilder, Expression enumerable, Action<T, int> foreachAct, Predicate<T> getBreakCondition = null, Predicate<T> getContinueCondition = null)
        {
            return ForEachRaw(exprBuilder, enumerable
                , b => b.InvokeAction<T, int>((obj, count) => foreachAct(obj, count), b.PInfo.Variables("itemVar"), b.PInfo.Variables("place"))
                , getBreakCondition, getContinueCondition);
        }

        /// <summary>
        /// 在子树中操作
        /// </summary>
        public static ExprBuilder DoChild(this ExprBuilder builder,out Expression childToLambda, Action<ExprBuilder> child)
        {
            var customBuilder = new ExprBuilder(builder);
            child?.Invoke(customBuilder);
            childToLambda = customBuilder.Lambda();
            return builder;
        }
        public static ExprBuilder DoChild<D>(this ExprBuilder builder, out Expression<D> childToLambda, Action<ExprBuilder> child)where D:Delegate
        {
            var customBuilder = new ExprBuilder(builder);
            child?.Invoke(customBuilder);
            childToLambda = customBuilder.Lambda<D>();
            return builder;
        }

        /// <summary>
        /// 保持链式调用，立即执行一个非表达式行为。
        /// </summary>
        public static ExprBuilder ChainAct(this ExprBuilder builder, Action act)
        {
            act?.Invoke();
            return builder;
        }
        /// <summary>
        /// （仅测试用）打印当前builder返回值
        /// </summary>
        public static ExprBuilder ChainTestLog(this ExprBuilder builder, params object[] args)
        {
            var obj = builder.Lambda().Compile().DynamicInvoke(args);
            if (obj != null) obj.Log();
            return builder;
        }
        /// <summary>
        /// （仅测试用）打印当前builder返回值
        /// </summary>
        public static ExprBuilder ChainTestLog<D>(this ExprBuilder builder, params object[] args) where D : Delegate
        {
            builder.Lambda<D>().Compile().DynamicInvoke(args).Log();
            return builder;
        }
    }
}